package com.example.user_manage.filter;

import ch.qos.logback.core.util.StringUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWTUtil;
import com.example.user_manage.constants.TempConstant;
import com.example.user_manage.constants.UrlConstants;
import com.example.user_manage.entity.User;
import com.example.user_manage.service.UserService;
import com.example.user_manage.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@AllArgsConstructor
@Component
public class TokenFilter extends OncePerRequestFilter {
    /**
     *用户服务对象
     */
    private final UserService userService;
    /**
     * redis数据库操作对象
     */
    private final RedisTemplate<String,Object> redisTemplate;

    /**
     * 过滤器内执行的操作
     * @param request 请求
     * @param response 响应
     * @param filterChain 过滤器链
     * @throws ServletException Servlet异常
     * @throws IOException IO异常
     */
    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain) throws ServletException, IOException {

        //创建url白名单列表
        List<RequestMatcher> publicMatchers = Arrays.stream(UrlConstants.WHITE_LIST)
                                            .map(AntPathRequestMatcher::new)
                                            .collect(Collectors.toList());
        //白名单内的rul全部放行
        if (publicMatchers.stream().anyMatch(matcher -> matcher.matches(request))) {
            filterChain.doFilter(request, response);
            return;
        }

        //指定响应格式
        response.setContentType("application/json; charset=utf-8");
        //接收token
        String token = request.getHeader("Token");

        //验证token是否为空字符串
        if (StringUtil.isNullOrEmpty(token)) {
            response.getWriter().write("传入的Token为空，请先登录");
            return;
        }

        //检测token是否被篡改
        boolean verigy = false;
        try{
            verigy = JWTUtil.verify(token, JwtUtil.secret.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            //记录错误信息
            log.error("异常信息：{}",e.getMessage());
        }
        if (!verigy) {
            response.getWriter().write("Token验证不通过，可能被篡改。请重新登录");
            return;
        }

        //从token中提取用户信息
        String userJson = JWTUtil.parseToken(token)
                .getPayloads().get("user",String.class);
        User user =JSONUtil.toBean(userJson, User.class);
        //获取用户角色列表
        user.setRoleList(userService.selectRolesByUserId(user.getId()));
        //通过用户id获取redis中存储的token
        String redisToken = (String) redisTemplate.opsForHash().get(
                TempConstant.REDIS_TOKEN_KEY
                ,user.getId().toString());
        if(token.equals(redisToken)){//比较两个token
            //创建用户的认证对象
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
            //将用户认证对象添加到Security框架的上下文，这样就可以通过Security框架了
            SecurityContextHolder.getContext().setAuthentication(authentication);
            //验证其他filter
            filterChain.doFilter(request, response);
        }else {
            response.getWriter().write("令牌与服务器令牌对不上");
        }
    }
}
